package cn.bitkit.mybatis;

import cn.bitkit.base.exception.BusinessAssert;
import cn.bitkit.mybatis.annotation.MultiQuery;
import cn.bitkit.mybatis.annotation.Query;
import cn.bitkit.mybatis.query.QueryCriteria;
import cn.bitkit.mybatis.entity.EntityClass;
import cn.bitkit.base.util.ReflectUtil;
import cn.bitkit.base.util.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author changbo
 */
@Slf4j
public class CriteriaHandler {

    public static String getWhereSql(Map<String, Object> paramMap){
        QueryCriteria criteria = (QueryCriteria) paramMap.get("criteria");
        BusinessAssert.isNotNull(criteria, "criteria must not be null");
        Object param = criteria.getParam();
        BusinessAssert.isNotNull(param, "criteria.param must not be null");
        String paramPath = "criteria.param.";
        List<Field> list = ReflectUtil.getFields(param.getClass());
        StringJoiner joiner = new StringJoiner("\n and ");
        for(Field field : list) {
            Object fieldValue = ReflectUtil.getValue(param.getClass(), field);
            //参数为空或者为false，不做为查询条件
            if(StringUtil.isEmpty(fieldValue) || Boolean.FALSE.equals(fieldValue)) {
                continue;
            }
            MultiQuery multiQuery = field.getAnnotation(MultiQuery.class);
            String comboSql = getSqlByMultiQuery(multiQuery, field, fieldValue, paramPath);
            if(StringUtil.hasValue(comboSql)){
                joiner.add(comboSql);
            }
            Query query = field.getAnnotation(Query.class);
            String sql = getSqlByQuery(query, field, fieldValue, paramPath);
            if(StringUtil.hasValue(sql)){
                joiner.add(sql);
            }
        }
        return joiner.toString();
    }

    public static String getSqlByMultiQuery(MultiQuery multiQuery, Field field, Object fieldValue, String paramPath){
        if(multiQuery == null || multiQuery.value().length == 0) {
            return null;
        }
        StringJoiner joiner = new StringJoiner( " " + multiQuery.logic().name() + " ", "(", ")");
        Arrays.stream(multiQuery.value()).forEach(query -> {
            String sql = getSqlByQuery(query, field, fieldValue, paramPath);
            if(StringUtil.hasValue(sql)){
                joiner.add(sql);
            }
        });
        return joiner.toString();
    }

    public static String getSqlByQuery(Query query, Field field, Object fieldValue, String paramPath){
        if(query == null) {
            return null;
        }
        if(query.ifValue().length > 0 && !List.of(query.ifValue()).contains(StringUtil.getValue(fieldValue))) {
            return null;
        }
        if(StringUtil.hasValue(query.sql())) {
            return query.sql();
        }
        String column = getColumn(query, field.getName());
        String fieldName = paramPath + field.getName();
        return query.type().process.getSql(query, fieldName, fieldValue, column);
    }

    public static String getColumn(Query query, String fieldName){
        if(StringUtil.hasValue(query.column())){
            return query.column();
        }
        return StringUtil.camelToUnderscore(fieldName);
    }

    public static String getLimit(Map<String, Object> map) {
        QueryCriteria criteria = (QueryCriteria) map.get("criteria");
        if(criteria.getLimit() != null && criteria.getLimit() > 0) {
            return "LIMIT " + criteria.getLimit();
        }
        return "";
    }

    public static String getOrderBy(Map<String, Object> map) {
        QueryCriteria criteria = (QueryCriteria) map.get("criteria");
        if(StringUtil.hasValue(criteria.getOrderBy())) {
            return "ORDER BY " + criteria.getOrderBy();
        }
        return "";
    }

    public static String getGroupBy(Map<String, Object> paramMap) {
        QueryCriteria criteria = (QueryCriteria) paramMap.get("criteria");
        if(StringUtil.hasValue(criteria.getGroupBy())) {
            return "GROUP BY " + criteria.getGroupBy();
        }
        return "";
    }

    public static String getSelect(EntityClass entityClass, Map<String, Object> paramMap){
        QueryCriteria criteria = (QueryCriteria) paramMap.get("criteria");
        if(StringUtil.hasValue(criteria.getSelect())){
            return criteria.getSelect();
        }
        return " * ";
    }

    public static String getFrom(EntityClass entityClass, Map<String, Object> map){
        QueryCriteria criteria = (QueryCriteria) map.get("criteria");
        if(StringUtil.hasValue(criteria.getFrom())){
            return criteria.getFrom();
        }
        return entityClass.getTableName();
    }

    public static String initList(Map<String, Object> map) {
        List<?> list = (List<?>) (map.get("list"));
        BusinessAssert.isNotEmpty(list, "param[list] can not be empty");
        AtomicInteger index = new AtomicInteger(0);
        return list.stream()
                .map(e -> "#{list["+index.getAndIncrement()+"]}")
                .collect(Collectors.joining(","));
    }
}
